package com.lry.jvm.rtda.heap;

import com.lry.jvm.classfile.ClassFile;
import com.lry.jvm.classpath.Classpath;
import com.lry.jvm.rtda.JObject;
import com.lry.jvm.rtda.LocalVars;
import com.lry.jvm.util.Util;

import java.util.HashMap;
import java.util.Map;

import static com.lry.jvm.rtda.heap.AccessFlags.ACC_PUBLIC;

public class JClassLoader {

    private Classpath cPath;
    private Map<String,JClass> classMap;

    public JClassLoader(Classpath cPath){
        this.cPath = cPath;
        this.classMap = new HashMap<>();
        loadBasicClasses();
        loadPrimitiveClasses();
    }

    //基本数据类型int等和数组一样都是运行时在jvm内部动态生成
    //生成的类名就是int,没有超类Object，没有父接口
    private void loadPrimitiveClasses() {
        for(String primitiveType:JClass.primitiveTypes.keySet()){
            loadPrimitiveClass(primitiveType);
        }
    }
//    非基本类型的类对象是通过ldc指令加载到操作数栈中的，将 在9.3.4节修改ldc指令，
//    让它支持类对象。而基本类型的类对象，虽 然在Java代码中看起来是通过字面量获取的，
//    但是编译之后的指令并不是ldc，而是getstatic。每个基本类型都有一个包装类，
//    包装类中 有一个静态常量，叫作TYPE，其中存放的就是基本类型的类
    private void loadPrimitiveClass(String className) {
        JClass cla = new JClass(ACC_PUBLIC,className,null,this,null,true);

        JObject jObject = new JObject(classMap.get("java/lang/Class"));
        jObject.setExtra(cla);
        cla.setJClass(jObject);
        classMap.put(className,cla);
    }


    //好迷这里？？？？？？？？？？？？？？？？？
    private void loadBasicClasses() {
        JClass jlClass = loadClass("java/lang/Class");
        for(JClass jClass:classMap.values()){
            if(null==jClass.getJClass()){
                JObject classObj = new JObject(jlClass);
                classObj.setExtra(jClass);
                jClass.setJClass(classObj);
            }
        }
    }

    public JClass loadClass(String name){
        if(classMap.containsKey(name)){
            return classMap.get(name);
        }
        JClass jClass;
        if(name.charAt(0)=='['){
            jClass = loadArrayClass(name);
        }else{
            jClass = loadNonArrayClass(name);
        }
        //迷？？？？？？？？？？？
        JClass jlClass = classMap.get("java/lang/Class");
        if(null!=jlClass){
            JObject classObj = new JObject(jlClass);
            classObj.setExtra(jClass);
            jClass.setJClass(classObj);
        }
        return jClass;
    }

    private JClass loadArrayClass(String name) {
        //数组类 extends Object implements Cloneable,Serializable,不需要初始化
        JClass jClass = new JClass(ACC_PUBLIC,name,
                loadClass("java/lang/Object"),this,
                new JClass[]{loadClass("java/lang/Cloneable"),loadClass("java/io/Serializable")},true);
        classMap.put(name,jClass);
        return jClass;
    }

    //加载非数组类
    private JClass loadNonArrayClass(String name) {
        byte[]data = readClass(name);
        JClass jClass = defineClass(data);
        link(jClass);
        return jClass;
    }

    private byte[] readClass(String name) {
        byte[] data = new byte[0];
        try {
            data = cPath.readClass(name);
        } catch (Exception e) {
            e.printStackTrace();
            Util.panic("readClass fail: className="+name);
        }
        return data;
    }

    private JClass defineClass(byte[] data) {
        JClass jClass = parseClass(data);
        jClass.setLoader(this);
        resolveSuperClass(jClass);
        resolveInterfaces(jClass);
        classMap.put(jClass.getName(),jClass);
        return jClass;
    }

    private void resolveInterfaces(JClass jClass) {
        int interfaceCount = jClass.getInterfaceNames().length;
        if(interfaceCount<=0){
            return;
        }
        JClass[] interfaces = new JClass[interfaceCount];
        for(int i=0;i<interfaces.length;i++){
            interfaces[i] = jClass.getLoader().loadClass(jClass.getInterfaceNames()[i]);
        }
        jClass.setInterfaces(interfaces);
    }

    private void resolveSuperClass(JClass jClass) {
        if(!jClass.getName().equals("java/lang/Object")){
            jClass.setSuperClass(jClass.getLoader().loadClass(jClass.getSuperClassName()));
        }
    }

    private JClass parseClass(byte[] data) {
        ClassFile cf = new ClassFile(data);
        return new JClass(cf);
    }

    private void link(JClass jClass) {
        verify(jClass);
        prepare(jClass);
    }

    private void prepare(JClass jClass) {
        //分配内存，赋予初始值
        calcInstanceFieldsSlotIds(jClass);
        calcStaticFieldsSlotIds(jClass);
        allocAndInitStaticVars(jClass);
    }

    private void allocAndInitStaticVars(JClass jClass) {
        if(jClass.getStaticSlotCount()<=0){
            return;
        }
        jClass.setStaticVars(new LocalVars(jClass.getStaticSlotCount()));

        for(JField field:jClass.getFields()){
            if(field.isStatic()&&field.isFinal()){
                initStaticFinalVar(jClass,field);
            }
        }
    }

    private void initStaticFinalVar(JClass jClass, JField field) {
        LocalVars staticVars = jClass.getStaticVars();
        RuntimeConstantPool rcp = jClass.getConstantPool();
        int cpIndex = field.getConstValueIndex();
        int slotId = field.getSlotId();

        if(cpIndex>0){
            switch (field.getDescriptor()){
                case "Z":
                case "B":
                case "C":
                case "S":
                case "I":
                    int intVar = (int) rcp.getConstant(cpIndex);
                    staticVars.setInt(slotId,intVar);
                    break;
                case "J":
                    long longVar = (long) rcp.getConstant(cpIndex);
                    staticVars.setLong(slotId,longVar);
                    break;
                case "F":
                    float floatVar = (float) rcp.getConstant(cpIndex);
                    staticVars.setFloat(slotId,floatVar);
                    break;
                case "D":
                    double doubleVar = (double) rcp.getConstant(cpIndex);
                    staticVars.setDouble(slotId,doubleVar);
                    break;
                case "Ljava/lang/String;":
                    String str = (String) rcp.getConstant(cpIndex);
                    JObject jStr = StringPool.JString(jClass.getLoader(),str);
                    staticVars.setRef(slotId,jStr);
                    break;
            }
        }
    }

    private void calcStaticFieldsSlotIds(JClass jClass) {
        int slotId = 0;
        for(JField field:jClass.getFields()){
            if(field.isStatic()){
                field.setSlotId(slotId);
                slotId++;
                if(field.isLongOrDouble()){
                    slotId++;
                }
            }
        }
        jClass.setStaticSlotCount(slotId);
    }

    private void calcInstanceFieldsSlotIds(JClass jClass) {
        int slotId = 0;
        if(jClass.getSuperClass()!=null){
            slotId = jClass.getSuperClass().getInstanceSlotCount();
        }

        for(JField field:jClass.getFields()){
            if(!field.isStatic()){
                field.setSlotId(slotId);
                slotId++;
                if(field.isLongOrDouble()){
                    slotId++;
                }
            }
        }
        jClass.setInstanceSlotCount(slotId);
    }

    private void verify(JClass jClass) {
        //校验
    }


}
